/****************************************************************************
 CurveText class
 copyright: Joachim Schnier, Flashscript.biz 2009
 This class will curve string along a path.
 ***********************************************************************/
package ctext
{
	import flash.geom.Point;
	import flash.text.engine.*;
	
	import spark.core.SpriteVisualElement;

	public class BezierText extends SpriteVisualElement
	{
		private var _text:String = '';
		private var _points:Array;
		private var _fit:Boolean = true;
		private var _spacing:Number = 2;
		private var _fontSize:int = 14;
		private var _color:uint = 0x000000;
		private var _fontFamily:String = "Arial";
		private var _textVec:Vector.<TextLine>;
			
		public function BezierText() {
			this.mouseEnabled = false;
			this.mouseChildren = false;			
		}
		
		public function get text():String { return _text; }
		public function set text(value:String):void {
			if(_text == value) return;
			_text = value;
			flushText();
			createText();
		}
		
		public function get points():Array { return _points; }
		public function set points(value:Array):void {
			if(_points == value) return;
			_points = value;
			createText();
		}
		
		public function get fit():Boolean { return _fit; }
		public function set fit(value:Boolean):void {
			if(_fit == value) return;
			_fit = value;
			createText();
		}
		
		public function get spacing():Number { return _spacing; }
		public function set spacing(value:Number):void {
			if(_spacing == value) return;
			_spacing = value;
			createText();
		}
		
		public function get fontSize():int { return _fontSize; }
		public function set fontSize(value:int):void {
			if(_fontSize == value) return;
			_fontSize = value;
			flushText();
			createText();
		}
		
		public function get color():uint { return _color; }
		public function set color(value:uint):void {
			if(_color == value) return;
			_color = value;
			flushText();
			createText();
		}
		
		public function get fontFamily():String { return _fontFamily; }
		public function set fontFamily(value:String):void {
			if(_fontFamily == value) return;
			_fontFamily = value;
			flushText();
			createText();
		}
		
		private function flushText():void {
			if(!_textVec) return;
			_textVec = null;
			while(this.numChildren > 0) this.removeChildAt(0);
		}
	
		private function createText():void {
			if(!_text) return;
			if(_text.length == 0) return;
			if(!_points) return;
			if(_points.length <= 1) return;
			
			var textLength:uint = _text.length;
			var tInterval:Number
			if(_fit) {
				tInterval = 0.001;
			} else {
				tInterval = textLength > 1 ? 1 / (textLength-1) : 0;
			}
			
			if(!_textVec) {
				// Set size and format
				var fontDescription:FontDescription = new FontDescription(_fontFamily);
				var format:ElementFormat = new ElementFormat(fontDescription);
				format.fontSize = fontSize;
				format.color = _color;
				
				_textVec = new Vector.<TextLine>(textLength);
				
				for(var j:int = 0; j < textLength; j++) {
					var ca:String = _text.charAt(j);
					
					/*
					* We create a TextElement for each letter and insert it into a TextBlock.
					* We select a high lineWidth to make sure that the width is large enough for 
					* the font size.
					*/
					var textElement:TextElement = new TextElement(ca, format);
					var textBlock:TextBlock = new TextBlock();
					textBlock.content = textElement;
					var textLine:TextLine = textBlock.createTextLine(null, 20);
					
					addChild(textLine);
					_textVec[j] = textLine;
				}
			}
			
			
			var usedTextLen:Number = 0;
			var usedCurveLen:Number = 0;
			var prevPoint:Point = _points[0];
			
			// Separate the string into individual letters.
			var t:Number = 0;
			var i:uint = 0;
			while(i < textLength) {
				var p1:Point = computeBezierOnPoints(t, 0, _points.length-2);
				var p2:Point = computeBezierOnPoints(t, 1, _points.length-1);
				var point:Point = Point.interpolate(p2, p1, t);
				
				if(!_fit || usedTextLen <= usedCurveLen) {
					var charLine:TextLine = _textVec[i];
					
					// position each textline to create the curve.
					charLine.x = point.x;
					charLine.y = point.y + 4;
					usedTextLen += Math.max(3, charLine.textWidth) + _spacing;
					
					// rotate each letter according to its position in the curve
					charLine.rotation = Math.atan2((p2.y - p1.y), (p2.x - p1.x)) * 180 / Math.PI;
					i++;
				}
				
				usedCurveLen += Point.distance(point, prevPoint);
				prevPoint = point;
				t += tInterval;
			}
			
			//drawPoints();
		}
		
		private function drawPoints():void {
			graphics.clear();
			graphics.beginFill(0xFF0000);
			for each(var point:Point in _points) {
				graphics.drawCircle(point.x, point.y, 3);
			}			
		}
		
		private function getPointAt(t:Number):Point {
			return computeBezierOnPoints(t, 0, _points.length-1);
		}
		
		private function getTanAt(t:Number):Number {
			var p1:Point = computeBezierOnPoints(t, 0, _points.length-2);
			var p2:Point = computeBezierOnPoints(t, 1, _points.length-1);
			return Math.atan2((p2.y - p1.y), (p2.x - p1.x)) * 180 / Math.PI;
		}
		
		private function computeBezierOnPoints(t:Number, from:int, to:int):Point {
			// compute bazier on points[from, to]
			var mt:Number = 1 - t;
			var n:int = to - from;
			var powT:Array = [1];
			var powMT:Array = [1];
			for(var p:int = 1; p <= n; p++) {
				powT[p] = powT[p-1] * t;
				powMT[p] = powMT[p-1] * mt;
			}
			
			var px:Number = 0;
			var py:Number = 0;
			var i:int = 0;
			for(var j:int = from; j <= to; j++) {
				var coef:Number = choose(n, i) * powT[i] * powMT[n-i];
				px += coef * _points[j].x;
				py += coef * _points[j].y;
				i++;
			}
			return new Point(px, py);
		}
		
		private function choose(n:uint, k:uint):Number {
			if(k > n) return 0;
			
			if (k > n/2) k = n-k; // Take advantage of symmetry
			
			var accum:Number = 1;
			for(var i:int = 1; i <= k; i++)
				accum = accum * (n-k+i) / i;
			
			return Math.floor(accum + 0.5); // avoid rounding error
		}
	}
}